#include "Fan.hpp"

#include <algorithm>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;


Fan::Fan(const vector<pair<string, string> >& envVars)
    : _envVars(envVars)
{
}


/***************************************
 *
 * Run
 *
 ***************************************/


int Fan::run(const vector<string>& programArgs)
{
  if (programArgs.empty())
  {
    Fan::help();
    return -1;
  }

  // Process args
  for (size_t i = 0; i < programArgs.size(); i++)
  {
    const string& arg = programArgs[i];
    if (arg.empty())
      continue;

    if (arg == "-help" || arg == "-h" || arg == "-?")
    {
      Fan::help();
      return 2;
    }
    else if (arg == "-version")
    {
      Fan::version("Fantom Launcher");
      return 3;
    }
    else if (arg == "-pods")
    {
      Fan::pods("Fantom Launcher");
      return 4;
    }
    else if (arg[0] == '-')
    {
      cout << "WARNING: Unknown option " << arg << endl;
    }
    else
    {
      const string& target = arg;
      vector<string> targetArgs(programArgs.begin() + i + 1, programArgs.end());
      return this->execute(target, targetArgs);
    }
  }

  Fan::help();
  return 2;
}


/***************************************
 *
 * Execute
 *
 ***************************************/


int Fan::execute(const string& target, const vector<string>& targetArgs)
{
  // Boot the 'sys' pod
  List* argList = List_create(Str_classType(), targetArgs.size());
  for (size_t i = 0; i < targetArgs.size(); i++)
  {
    Str* argStr = Str_createFromCStr(targetArgs[i].c_str());
    List_add(argList, VAL_MAKE_REF(argStr));
  }
  Map* envMap = Map_create(Str_classType(), Str_classType());
  for (size_t i = 0; i < _envVars.size(); i++)
  {
    Str* varName = Str_createFromCStr(_envVars[i].first.c_str());
    Str* varValue = Str_createFromCStr(_envVars[i].second.c_str());
    Map_set(envMap, VAL_MAKE_REF(varName), VAL_MAKE_REF(varValue));
  }
  Sys_boot(argList, envMap);

  // First try as file name
  string lowerTarget = target;
  transform(lowerTarget.begin(), lowerTarget.end(), lowerTarget.begin(), ::tolower);
  size_t index = lowerTarget.rfind(".fan");
  bool targetHasFanExt = (index == (lowerTarget.size() - 4));

  struct stat fileInfo;
  bool fileExists = (stat(target.c_str(), &fileInfo) == 0);
  if (targetHasFanExt && fileExists && !S_ISDIR(fileInfo.st_mode))
    return this->executeFile(target, targetArgs);
  else
    return this->executeType(target, targetArgs);
}


int Fan::executeFile(const string& filename, const vector<string>& targetArgs)
{
  //TODO: IMPL Fan::executeFile
  return 0;
}


int Fan::executeType(const string& target, const vector<string>& targetArgs)
{
  string fullTarget;
  if (target.find("::") == string::npos)
    fullTarget = target + "::Main.main";
  else if (target.find('.') == string::npos)
    fullTarget = target + ".main";
  else
    fullTarget = target;

  size_t dot = fullTarget.find('.');
  string typeName = fullTarget.substr(0, dot);
  string methodName = fullTarget.substr(dot + 1);
  Type* type = Type_find(Str_createFromCStr(typeName.c_str()), true);
  Method* main = Type_method(type, Str_createFromCStr(methodName.c_str()), true);
  return this->callMain(type, main);
}


int Fan::callMain(Type* t, Method* m)
{
  // Check parameter type and build main arguments
  List* args = NULL;
  List* params = Method_params(m);
  if (List_size(params) > 0)
  {
    Param* firstParam = (Param*)VAL_GET_REF(List_getSafe(params, 0, VAL_NULL));
    Param* secondParam = (Param*)VAL_GET_REF(List_getSafe(params, 1, VAL_NULL));
    bool firstParamIsStrList = Type_fits(Param_type(firstParam), Type_toListOf(Str_classType()));
    if (firstParamIsStrList && (List_size(params) == 1 || Param_hasDefault(secondParam)))
      args = Env_args(Env_cur());
    else
    {
      cout << "ERROR: Invalid parameters for main: " << Str_cStr(Slot_signature((Slot*)m)) << endl;
      return -1;
    }
  }

  // Invoke
  NullableObj res;
  if (Slot_isStatic((Slot*)m))
    res = Method_callList(m, args);
  else
    res = Method_callOn(m, Type_make(t, NULL), args);
  return (VAL_GET_TYPE(res) == ValType_Int) ? VAL_GET_INT(res) : 0;
}


//////////////////////////////////////////////////////////////////////////
// Version, pods, help
//////////////////////////////////////////////////////////////////////////


void Fan::version(const string& progName)
{
  //TODO: IMPL Fan::version
}


void Fan::pods(const string& progName)
{
  //TODO: IMPL Fan::version
}


void Fan::help()
{
  cout << "Fantom Launcher" << endl;
  cout << "Usage:" << endl;
  cout << "  fan-llvm [options] <pod>::<type>.<method> [args]*" << endl;
  cout << "  fan-llvm [options] <filename> [args]*" << endl;
  cout << "Options:" << endl;
  cout << "  -help, -h, -?  print usage help" << endl;
  cout << "  -version       print version information" << endl;
  cout << "  -pods          list installed pods" << endl;
}
